/*
 * inotify-sync - a simple file synchronizer and file system watcher
 * Copyright (C) 2010-2011, inotify-sync developers and inotify-sync contributors
 * Copyright (C) 2010-2011, Cohesion Network Security Studio
 *
 * inotify-sync is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * inotify-sync is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with inotify-sync; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

/*
 * FILE:			Inotify.cpp
 * USAGE:			---
 * DESCRIPTION:		---
 * OPTIONS:			---
 * REQUIREMENTS:	---
 * BUGS:			---
 * NOTES:			---
 * AUTHOR:			LI Yiwei (mail:leeyiw@gmail.com)
 * COMPANY:			---
 * VERSION:			1.0
 * CREATED:			Thu Sep  1 13:42:03 CST 2011
 * REVISION:		---
 */

#include "Inotify.h"

Inotify::Inotify(Profile& profile)
try
{
	this->profile=profile;
	ignore_path_vec=profile.get_ignore_path_vec();
	fd=inotify_init();
	if(fd==-1)
		throw Error(errno,E_INOTINITFAIL);
	init_dir(profile.get_src_path());

	int err=pthread_rwlock_init(&ievent_p_vec_lock,NULL);
	if(err!=0)
		throw Error(err,E_INITOFRWLOCK);
	err=pthread_rwlock_init(&dnode_vec_lock,NULL);
	if(err!=0)
		throw Error(err,E_INITOFRWLOCK);
}
catch(Error& error)
{
	error.print_error();
	exit(error.get_error_id());
}

Inotify::~Inotify()
try
{
	int err=pthread_rwlock_destroy(&ievent_p_vec_lock);
	if(err!=0)
		throw Error(err,E_DESTOFRWLOCK);
	err=pthread_rwlock_destroy(&dnode_vec_lock);
	if(err!=0)
		throw Error(err,E_DESTOFRWLOCK);

	close(fd);
}
catch(Error& error)
{
	error.print_error();
	exit(error.get_error_id());
}

int
Inotify::init_dir(const std::string root_dir)
{
	int err=pthread_rwlock_wrlock(&dnode_vec_lock);
	if(err!=0)
		pthread_exit((void *)E_GETWRLOCKERR);
	
	nftw(root_dir.c_str(),
		do_init_dir,
		500,
		FTW_PHYS);

	err=pthread_rwlock_unlock(&dnode_vec_lock);
	if(err!=0)
		pthread_exit((void *)E_GETUNLOCKERR);

	return 0;
}

int
Inotify::destory_dir(const std::string root_dir)
{
	int err=pthread_rwlock_wrlock(&dnode_vec_lock);
	if(err!=0)
		pthread_exit((void *)E_GETWRLOCKERR);

	std::vector<DirNode>::iterator iter;
	for(iter=dnode_vec.begin();iter<dnode_vec.end();iter++)
	{
		if((iter->abs_path).substr(0,root_dir.size())==root_dir)
			dnode_vec.erase(iter);
	}

	err=pthread_rwlock_unlock(&dnode_vec_lock);
	if(err!=0)
		pthread_exit((void *)E_GETUNLOCKERR);
	return 0;
}

void
Inotify::monitor(time_t r_time)
{
	//注册中断处理程序sig_alrm
	if(signal(SIGALRM,sig_alrm)==SIG_ERR)
		return;
	//设置定时器
	if(sigsetjmp(env_alrm,1)!=0)
	{
		alarm(0);
		printf("inotify-sync: monitor time out, stop monitor!\n");
		return;
	}
	if(r_time>0)
		alarm(r_time);

	int err;

	func trans_func;
	try
	{
		//创建线程read_events和handle_events
		pthread_t read_events_tid;
		trans_func.member_func=&Inotify::read_events;
		err=pthread_create(&read_events_tid,NULL,
				trans_func.alone_func,this);
		if(err!=0)
			throw Error(err,E_CREATETHREAD);

		pthread_t handle_events_tid;
		trans_func.member_func=&Inotify::handle_events;
		err=pthread_create(&handle_events_tid,NULL,
				trans_func.alone_func,this);
		if(err!=0)
			throw Error(err,E_CREATETHREAD);

		//join线程read_events和handle_events
		void *read_events_ret;
		err=pthread_join(read_events_tid,&read_events_ret);
		if(err!=0)
			throw Error(err,E_JOINTHREADER);
		void *handle_events_ret;
		err=pthread_join(read_events_tid,&handle_events_ret);
		if(err!=0)
			throw Error(err,E_JOINTHREADER);
	}
	catch(Error& error)
	{
		error.print_error();
		exit(error.get_error_id());
	}
}

void *
Inotify::read_events()
{
	int length=0;
	char buf[1000]={0};
	while((length=read(fd,buf,sizeof(buf)))!=-1)
	{
		int buf_offset=0;
		struct inotify_event *p_event=0,*event=0;
		while(buf_offset<length)
		{
			p_event=(struct inotify_event *)&buf[buf_offset];
			size_t event_size;
			event_size=offsetof(struct inotify_event,name)
							+p_event->len;
			event=(struct inotify_event *)malloc(event_size);
			memcpy(event,p_event,event_size);

//对ievent_p_vec进行加锁并push_back
			int err=pthread_rwlock_wrlock(&ievent_p_vec_lock);
			if(err!=0)
				pthread_exit((void *)E_GETWRLOCKERR);
			ievent_p_vec.push_back(event);
			err=pthread_rwlock_unlock(&ievent_p_vec_lock);
			if(err!=0)
				pthread_exit((void *)E_GETUNLOCKERR);

			buf_offset+=event_size;
		}
	}
	return ((void *)0);
}

void *
Inotify::handle_events()
{
	int err;
	while((err=pthread_rwlock_wrlock(&ievent_p_vec_lock))==0)
	{
		if(ievent_p_vec.size()>0)
		{
			p_event_handler->handle_events(&dnode_vec,
							&ievent_p_vec);
			for(size_t i=0;i<ievent_p_vec.size();i++)
				free(ievent_p_vec.at(i));
			ievent_p_vec.clear();
		}
		pthread_rwlock_unlock(&ievent_p_vec_lock);
		if(err!=0)
			pthread_exit((void *)E_GETUNLOCKERR);
	}
	if(err!=0)
		pthread_exit((void *)E_GETWRLOCKERR);
	return ((void *)0);
}

void
Inotify::get_event_handler(EventHandler *p_event_handler)
{
	this->p_event_handler=p_event_handler;
}

int
do_init_dir(const char *fpath,
		const struct stat *sb,
		int typeflag,
		struct FTW *ftwbuf)
{
	if(typeflag==FTW_F)
		return 0;
	for(size_t i=0;i<ignore_path_vec.size();i++)
	{
		if(ignore_path_vec[i]==fpath)
			return 0;
	}
	int wd=inotify_add_watch(fd,fpath,
					IN_CREATE
					|IN_DELETE
					|IN_MODIFY
					|IN_MOVE_SELF
					|IN_MOVED_FROM
					|IN_MOVED_TO
					);
//	printf("%s\n",fpath);
	if(wd==-1)
	{
		printf("%d\n",errno);
		perror("inotify_add_watch");
	}
	DirNode node={wd,fpath};
	//s_dnode_vec.push_back(node);
	dnode_vec.push_back(node);
	return 0;
}

void
sig_alrm(int signo)
{
	siglongjmp(env_alrm,1);
}
